/*
 * @author 渡一
 * @date 2024-12-03 21:03:07
 * @description 父子组件双向绑定时，为了不破坏单向数据流，使用v-model钩子函数
 * @see 🚀
 * @status done
 */

import { computed } from 'vue'

interface Props {
  [key: string]: any
}

const cacheMap = new WeakMap()

/**
 * 创建v-model钩子函数；
 * 表单组件使用双向绑定时，子组件在修改数据时，修改了本不是自己的数据，打破了单向数据流原则；
 * @param props 传入的props对象
 * @param propName 需要创建v-model的属性名
 * @param emit 触发事件的方法
 * @returns
 */
export function useVModel<T extends Props, K extends keyof T & string>(
  // props：传入的props对象
  props: T,
  // propName：需要创建v-model的属性名
  propName: K,
  // emit：触发事件的方法
  emit: (event: `update:${K}`, ...args: any[]) => void
) {
  // 返回一个计算属性
  return computed({
    // 获取属性值
    get() {
      // 如果缓存中存在该属性值，则直接返回
      if (cacheMap.has(props[propName])) {
        return cacheMap.get(props[propName])
      }

      // 创建一个代理对象
      const proxy = new Proxy(props[propName], {
        // 获取属性值
        get(target, key) {
          return Reflect.get(target, key)
        },
        // 设置属性值
        set(target, key, value) {
          // 触发事件，更新属性值
          emit(`update:${propName}`, {
            ...target,
            [key]: value
          })
          return true
        }
      })

      // 将代理对象存入缓存
      cacheMap.set(props[propName], proxy)
      return proxy
    },

    // 设置属性值
    set(val) {
      // 触发事件，更新属性值
      emit(`update:${propName}`, val)
    }
  })
}
